package com.esunny.online.util;

import com.esunny.online.domain.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static io.jsonwebtoken.Jwts.builder;
import static io.jsonwebtoken.Jwts.parser;

/**
 * @author ：李先生
 * @date ：Created in 2020/7/23 10:10
 * @description：
 * @modified By：
 * @version: $
 */
public final class JwtUtil {


    /**
     * 密钥
     */
    private static String jwtSecret = ConstantUtil.JWT_SECRET;


    /**
     * 固定预期时间 7天
     */
    private static int expire = ConstantUtil.JWT_EXPIRE_SECOND;


    /**
     * 指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
     */
    private static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    /**
     * 固定时间的令牌，expire
     *
     * @param user 用户
     * @return jwt令牌
     */
    public static String createJwt(User user) {
        return createJwt(expire * 1000, user);
    }

    /**
     * 用户登录成功后生成Jwt
     * 使用Hs256算法  私匙使用用户密码
     *
     * @param ttlMillis jwt过期时间
     * @param user      用户
     * @return Token令牌
     */
    public static String createJwt(long ttlMillis, User user) {

        //生成Jwt的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        Map<String, Object> claims = new HashMap<>(5);
        claims.put("id", user.getId());
        claims.put("username", user.getUsername());
        claims.put("realname",user.getRealname());
        claims.put("level",user.getLevel());
        claims.put("studytime",user.getStudytime());

        //生成签名的时候使用的秘钥secret,这个方法本地封装了的，一般可以从本地配置文件中读取，切记这个秘钥不能外露哦。它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        String key = jwtSecret;

        //生成签发人
        String subject = user.getUsername();

        //下面就是在为payload添加各种标准声明和私有声明了
        //这里其实就是new一个JwtBuilder，设置jwt的body
        JwtBuilder builder = builder()
                //如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setClaims(claims)
                //设置jti(Jwt ID)：是Jwt的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
                .setId(UUID.randomUUID().toString())
                //iat: jwt的签发时间
                .setIssuedAt(now)
                //代表这个Jwt的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
                .setSubject(subject)
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, key);

        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            //设置过期时间
            builder.setExpiration(exp);
        }
        return builder.compact();
    }


    /**
     * Token的解密
     *
     * @param token 加密后的token
     * @return Claims
     */
    public static Claims parseJwt(String token) {
        //签名秘钥，和生成的签名的秘钥一模一样
        String key = jwtSecret;

        //得到DefaultJwtParser
        Claims claims = parser()
                //设置签名的秘钥
                .setSigningKey(key)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

    /**
     * 通过令牌转化User
     *
     * @param token 令牌
     * @return user
     */
    public static User parseJwtToUser(String token) {
        Claims claims = parseJwt(token);

        if (claims != null || claims.get("id") != null || claims.get("username") != null) {
            Integer id = (Integer) claims.get("id");
            String username = (String) claims.get("username");
            User user = new User();
            user.setId(id);
            user.setUsername(username);
            user.setRealname((String) claims.get("realname"));
            user.setLevel((Integer) claims.get("level"));
            user.setStudytime((String) claims.get("studytime"));
            return user;
        }
        return null;
    }


    /**
     * 校验token
     * 在这里可以使用官方的校验，我这里校验的是token中携带的密码于数据库一致的话就校验通过
     *
     * @param token 令牌
     * @param user  用户
     * @return 布尔值
     */
    public static Boolean isVerify(String token, User user) {
        //签名秘钥，和生成的签名的秘钥一模一样
        String key = jwtSecret;

        //得到DefaultJwtParser
        Claims claims = parser()
                //设置签名的秘钥
                .setSigningKey(key)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();

        return claims.get("username").equals(user.getUsername());
    }


}
